#include "temu.h"
#include "expr.h"

/* We use the POSIX regex functions to process regular expressions.
 * Type 'man regex' for more information about POSIX regex functions.
 */
#include <sys/types.h>
#include <regex.h>

enum {
	NOTYPE = 256, EQ, UEQ, ADDR, XNUM, NUM, STR, DEREF, NEG, LOGNOT, REG, AND, OR, XOR

	/* TODO: Add more token types */

};

static struct rule {
	char *regex;
	int token_type;
} rules[] = {

	/* TODO: Add more rules.
	 * Pay attention to the precedence level of different rules.
	 */

	{" +", NOTYPE},						// spaces
	{"==", EQ},							// equal
	{"!=", UEQ},						// unequal
	{ "\\*0x[0-9a-fA-F]+", ADDR},		// address
	{ "0x[0-9a-fA-F]+", XNUM},			// hex
	{"[0-9]+", NUM},					// number
	{"\\!", LOGNOT},					// logical not	
	{"\\$[0-9A-z]{2,4}", REG},			// reg
	{"[0-9A-z]+", STR},					// string
	{"\\&\\&", AND},					// and
	{"\\|\\|", OR},						// or
	{"\\^", XOR},						// xor
	{"\\(", '('},						// left partentheses
	{"\\)", ')'},						// right partentheses
	{"\\*", '*'},						// mut
	{"\\/", '/'},						// div
	{"\\+", '+'},						// plus
	{"\\-", '-'},						// minus
};

#define NR_REGEX (sizeof(rules) / sizeof(rules[0]) )

static regex_t re[NR_REGEX];

/* Rules are used for many times.
 * Therefore we compile them only once before any usage.
 */
void init_regex() {
	int i;
	char error_msg[128];
	int ret;

	for(i = 0; i < NR_REGEX; i ++) {
		ret = regcomp(&re[i], rules[i].regex, REG_EXTENDED);
		if(ret != 0) {
			regerror(ret, &re[i], error_msg, 128);
			Assert(ret == 0, "regex compilation failed: %s\n%s", error_msg, rules[i].regex);
		}
	}
}

typedef struct token {
	int type;
	char str[32];
} Token;

Token tokens[32];
int nr_token;

static bool make_token(char *e) {
	int position = 0;
	int i;
	regmatch_t pmatch;
	
	nr_token = 0;

	while(e[position] != '\0') {
		/* Try all rules one by one. */
		for(i = 0; i < NR_REGEX; i ++) {
			if(regexec(&re[i], e + position, 1, &pmatch, 0) == 0 && pmatch.rm_so == 0) {
				char *substr_start = e + position;
				int substr_len = pmatch.rm_eo;

				// Log("match rules[%d] = \"%s\" at position %d with len %d: %.*s", i, rules[i].regex, position, substr_len, substr_len, substr_start);
				position += substr_len;

				/* TODO: Now a new token is recognized with rules[i]. Add codes
				 * to record the token in the array `tokens'. For certain types
				 * of tokens, some extra actions should be performed.
				 */

				switch(rules[i].token_type) {
					case NOTYPE: break;
					default: // panic("please implement me");
						{
							tokens[nr_token].type = rules[i].token_type;
							sprintf(tokens[nr_token].str, "%.*s", substr_len, substr_start);
							++nr_token;
						}
				}
				break;
			}
		}

		if(i == NR_REGEX) {
			printf("no match at position %d\n%s\n%*.s^\n", position, e, position, "");
			return false;
		}
	}

	return true; 
}

static bool exprError;

int findDominantOperator(int p, int q){
	int op = -1;
	int rank = 6;
	int part = 0;
	int i;
	for(i = q - 1; i >= p; -- i){
		if(tokens[i].type == '(') ++part;
		else if(tokens[i].type == ')') --part;

		if((part == 0) && (tokens[i].type == LOGNOT || tokens[i].type == NEG) && (rank > 5)){
			rank = 5;
			op = i;
		}
		else if((part == 0) && ((tokens[i].type == '*') || (tokens[i].type == '/')) && (rank > 4)){
			rank = 4;
			op = i;
		}
		else if((part == 0) && ((tokens[i].type == '+') || (tokens[i].type == '-')) && (rank > 3)){
			rank = 3;
			op = i;
		}
		else if((part == 0) && ((tokens[i].type == EQ) || (tokens[i].type == UEQ)) && (rank > 2)){
			rank = 2;
			op = i;
		}
		else if((part == 0) && ((tokens[i].type == AND) || (tokens[i].type == XOR)) && (rank > 1)){
			rank = 1;
			op = i;
		}
		else if((part == 0) && (tokens[i].type == OR) && (rank > 0)){
			rank = 0;
			op = i;
		}
	}
	return op;
}

int checkPartentheses(int p, int q){
	int i;
	int lpth = 0;
	if((tokens[p].type != '(') || (tokens[q].type != ')')){
		return false;
	}
	for(i = p; i <= q; ++ i){
		if((lpth == 0) && (i != p) && (i != q)){
			exprError = true;
			return false;
		}
		if(tokens[i].type == '(') ++lpth;
		else if(tokens[i].type == ')') --lpth;
	}
	if(lpth == 0){
		return true;
	}
	return false;
}

int exprVal(int p, int q){
	if(exprError == true){
		return 0;
	}
	if(p > q){
		exprError = true;
		return 0;
	}
	else if(p == q){
		int val = 0;
		switch(tokens[p].type){
			case XNUM: sscanf(tokens[p].str, "0x%x", &val); return val;
			case NUM: sscanf(tokens[p].str, "%d", &val); return val;
			case ADDR: sscanf(tokens[p].str, "*0x%x", &val); return mem_read(val, 4);
			case REG: if(strcmp(tokens[p].str, "$pc") == 0) return cpu.pc;
		}
		int i;
		for(i = R_ZERO; i < R_RA; ++ i){
			if(strcmp(tokens[p].str, regfile[i]) == 0) return reg_w(i);
		}
	}
	else if(checkPartentheses(p, q) == true){
		return exprVal(p + 1, q - 1);
	}
	else {
		int op;
		int n1, n2;
		op = findDominantOperator(p, q);
		n2 = exprVal(op + 1, q);
		if(tokens[op].type == LOGNOT)return (!n2);
		if(tokens[op].type == NEG)return (-n2);
		if(tokens[op].type == DEREF)return mem_read(n2, 4);
		n1 = exprVal(p, op - 1);
		switch(tokens[op].type){
			case '+':return n1 + n2;
			case '-':return n1 - n2;
			case '*':return n1 * n2;
			case '/':return n1 / n2;
			case AND:return n1 &n2;
			case OR:return n1 | n2;
			case XOR:return n1 ^ n2;
			case EQ :return n1 == n2;
			case UEQ :return n1 != n2;
		}
	}
	return 0;
}

int isOperator(int i){
	if(i < 0) return true;
	switch(tokens[i].type){
		case EQ: case UEQ: case LOGNOT: case AND: case OR: case XOR: 
		case '(': case '*': case '/': case '+': case '-': return true;
		default: return false;
	}
}

uint32_t expr(char *e, bool *success) {
	if(!make_token(e)) {
		*success = false;
		return 0;
	}

	/* TODO: Insert codes to evaluate the expression. */
	// panic("please implement me");

	exprError = false;
	int exprRes = 0;
	int i;
	for(i = 0; i < nr_token; i++){
		if(tokens[i].type == STR){
			GUI_printf(RED_TEXT, "unexpected string \"%s\" in expression.", tokens[i].str);
			*success = false;
			return -1;
		}
		if((tokens[i].type == '-') && isOperator(i - 1)){
			tokens[i].type = NEG;
		}
		if(tokens[i].type == '*' && isOperator(i - 1)){
			tokens[i].type = DEREF;
		}
	}
	
	exprRes = exprVal(0, nr_token - 1);
	*success = !exprError;
	return exprRes;
}

